#include "utils.h"
#include "ray.h"	// Vector3 from vector.h cascaded
//include "vector.h"
#include <cstdio>
#include <cctype>
#include <cstring>
#include <cassert>

void skipToEOL(FILE* f, char* c)
{
	// returns *c = '\n'
	
	while (*c != '\n' && !feof(f) && !ferror(f))
		*c = fgetc(f);
}

void skipToNextChar(FILE* f, char* c)
{
	// returns the char
	do
	{
		*c = fgetc(f);
	} while (isspace((int)(*c)) && !feof(f) && !ferror(f));
}


void skipCurrentAndEmptyLines(FILE* f, char* c)
{
	skipToEOL(f, c);
	if (feof(f) || (ferror(f)))
		return;
	skipToNextChar(f, c);	// skip empty lines and space
}

void InitObjectsFromFile(const char* filename, int sceneId)
{
	/* 
	Lines starting with a ; are ignored (but end of line comments not supported)
	Default color is white if none specified
		Colors specified using hexadecimal notation (#RRGGBB)
		Color/reflection/specularity optional, but preceding values must be specified before desired

	Scene #
		specifies the scene in which to place objects following this line, with default scene = 1
		No object can belong to more than one scene
	Sphere ox oy oz radius [color reflection specularity] 
		a sphere with origin <ox, oy, oz>, radius, and optional modifiers
	Plane nx ny nz distance [color reflection specularity]
		a plane with normal <nx, ny, nz> and distance from origin
	Light ox oy oz radius [color reflection specularity]
		a light with origin <ox, oy, oz>, and sphere radius
	Disc nx ny nz ox oy oz radius [color reflection specularity]
		a disc with normal <nx, ny, nz>, origin <ox, oy, oz> and radius
	Box ox oy oz x y z [color reflection specularity]
		a box at origin <ox, oy, oz> with dimensions x y z
	File "file name"
		adds objects in file name to current scene
	*/
	int i;
	int ival;
	float x,y,z,d;
	int r,g,b;
	char c;
	char buf[80];
	FILE* f;
	char nfname[80];

	Vector3 origin, normal;
	Color color;
	Object *ob;
	Object* curScene;		// array of objects rather than pointer to one

	if (strcmp(filename, "-") == 0)
		f = stdin;
	else
		f = fopen(filename, "r");

	if (!f)
	{
		printf("Unable to open scene/object configuration file %s\n", filename);
		return;
	}

	if (sceneId == 0)		// called for the first time, do setup
	{
		for (i=0; i < MAX_OBJECTS; i++)
		{
			Scene1[i].active = 0;
			Scene2[i].active = 0;
		}
		sceneId = 1;		// default scene is 1
	}
	curScene = (sceneId == 1 ? Scene1 : Scene2);
	
	skipToNextChar(f, &c);
	while (!feof(f) && !ferror(f))
	{
		if (c == ';')
		{
			skipCurrentAndEmptyLines(f, &c);
			continue;
		}
		ungetc(c, f);

		// default color
		color.r = color.g = color.b = 1.0f;
		ob = NULL;
		// read the command
		i = fscanf(f, "%s", buf);
		if (strcmp(buf, "Scene") == 0)
		{
			i = fscanf(f, "%d", &ival);
#ifdef DEBUG
			printf("Switching scene from %d to %d ...", sceneId, ival);
#endif
			switch (ival)
			{
				case 1:
					sceneId = ival;
					curScene = Scene1;
					break;
				case 2:
					sceneId = ival;
					curScene = Scene2;
					break;
				default:
					// error
#ifdef DEBUG
					printf(" invalid scene id!");
#endif
					break;
			}
#ifdef DEBUG
			putchar('\n');
#endif
			skipCurrentAndEmptyLines(f, &c);
			continue;
		}
		else if (strcmp(buf, "Sphere") == 0)
		{
			i = fscanf(f, "%f %f %f %f", &x, &y, &z, &d);
			VectorSet(origin, x, y, z);
#ifdef DEBUG
			printf("Adding sphere to scene %d\n", sceneId);
			printf("\torigin   = <%.1f, %.1f, %.1f>\n", x, y, z);
			printf("\tradius   = %.1f\n", d);
#endif
			ob = AddSphere(curScene, origin, d, color);
		}
		else if (strcmp(buf, "Plane") == 0)
		{
			i = fscanf(f, "%f %f %f %f", &x, &y, &z, &d);
			VectorSet(normal, x, y, z);
#ifdef DEBUG
			printf("Adding plane to scene %d\n", sceneId);
			printf("\tnormal   = <%.1f, %.1f, %.1f>\n", x, y, z);
			printf("\tdistance = %.1f\n", d);
#endif
			Normalize(normal);
			ob = AddPlane(curScene, normal, d, color);
		}
		else if (strcmp(buf, "Light") == 0)
		{
			i = fscanf(f, "%f %f %f %f", &x, &y, &z, &d);
			VectorSet(origin, x, y, z);
#ifdef DEBUG
			printf("Adding light to scene %d\n", sceneId);
			printf("\torigin   = <%.1f, %.1f, %.1f>\n", x, y, z);
			printf("\tradius   = %.1f\n", d);
#endif
			ob = AddLight(curScene, origin, d, color);
		}
		else if (strcmp(buf, "Disc") == 0)
		{
			i = fscanf(f, "%f %f %f", &x, &y, &z);
			VectorSet(normal, x, y, z);
#ifdef DEBUG
			printf("Adding disc to scene %d\n", sceneId);
			printf("\tnormal   = <%.1f, %.1f, %.1f>\n", x, y, z);
#endif
			Normalize(normal);
			i = fscanf(f, "%f %f %f %f", &x, &y, &z, &d);
			VectorSet(origin, x, y, z);
#ifdef DEBUG
			printf("\torigin   = <%.1f, %.1f, %.1f>\n", x, y, z);
			printf("\tdistance = %.1f\n", d);
#endif
			ob = AddDisc(curScene, normal, origin, d, color);
		}
		else if (strcmp(buf, "Box") == 0)
		{
			i = fscanf(f, "%f %f %f", &x, &y, &z);
			VectorSet(origin, x, y, z);
#ifdef DEBUG
			printf("Adding box to scene %d\n", sceneId);
			printf("\tnormal   = <%.1f, %.1f, %.1f>\n", x, y, z);
#endif
			i = fscanf(f, "%f %f %f", &x, &y, &z);
#ifdef DEBUG
			printf("\tdim. xyz = %.1f %.1f %.1f\n", x, y, z);
#endif
			ob = AddBox(curScene, origin, x, y, z, color);
		}
		else if (strcmp(buf, "File") == 0)
		{
			// skip leading whitespace, then "file name relative to path of current specified file, in quotes"
			i = fscanf(f, " \"%[^\"]\"", buf);

			// extract path prefix
			for (i = strlen(filename)-1; i >= 0; i--)
			{
				if (filename[i] == '/')		// forward slash for directory separator
					break;
			}

			// i >= 0 if there is a slash
			if (i >= 0)
				strncpy(nfname, filename, i+1); 

			nfname[i+1] = '\0';			// if no slash, i == -1
			strcat(nfname, buf);

#ifdef DEBUG
			printf("Adding file %s\n", nfname);
#endif
			InitObjectsFromFile(nfname, sceneId);

			skipCurrentAndEmptyLines(f, &c);
			continue;
		}
		else
		{
#ifdef DEBUG
			printf("Unknown command \"%s\"; skipping...\n", buf);
#endif
			skipCurrentAndEmptyLines(f, &c);
			continue;
		}

		assert( ob != NULL );		// TODO NULL means all objects in scene full

		skipToNextChar(f, &c);
		if (c == '#')
		{
			// colors
			i = fscanf(f, "%2X%2X%2X", &r, &g, &b);
			assert(i == 3);
			ob->color.r = (float)r/255.0f;
			ob->color.g = (float)g/255.0f;
			ob->color.b = (float)b/255.0f;

			// assuming next valid is an alphabet char (or ';'), then floats should be expected 
			i = fscanf(f, "%f", &d);
			if (i == 1)
			{
				ob->reflect = d;
				i = fscanf(f, "%f", &d);
				if (i == 1)
					ob->specular = d;
				else
					ungetc('\n', f);
			}
			else
				ungetc('\n', f);	// scanf reads all whitespace
		}
		else
		{
			ungetc(c, f);
			ungetc('\n', f);
		}

#ifdef DEBUG
		printf("\tcolor    = %.1f %.1f %.1f\n", ob->color.r, ob->color.g, ob->color.b);
		printf("\treflect  = %.1f\n\tspecular = %.1f\n", ob->reflect, ob->specular);
#endif

		skipCurrentAndEmptyLines(f, &c);
	}
}

void InitObjects (void)
{
	int i;
	Vector3 origin, normal;
	Color color;
	Object *ob;
	for (i=0; i < MAX_OBJECTS; i++)
	{
		Scene1[i].active = 0;
		Scene2[i].active = 0;
	}

	VectorSet(origin, 0, 0, 250);
	color.r = 1.0f;
	color.g = 1.0f;
	color.b = 1.0f;
	AddSphere(Scene1, origin, 50, color);

	VectorSet(origin, 10, 100, 225);
	color.r = 1.0f;
	color.g = 1.0f;
	color.b = 1.0f;
	AddSphere(Scene1, origin, 20, color);

	VectorSet(origin, 125, 20, 250);
	color.r = color.b = 0.0f;
	color.g = 1.0f;
	ob = AddSphere(Scene1, origin, 50, color);
	ob->reflect = 0.2f;
	ob->specular = 1.0f;

	VectorSet(origin, 20, 200, 200);
	color.r = 1.0f;
	color.g = 1.0f;
	color.b = 1.0f;
	AddLight(Scene1, origin, 750, color);

	VectorSet(origin, 50, 150, 150);
	color.r = 1.0f;
	color.g = 0.0f;
	color.b = 0.0f;
	AddLight(Scene1, origin, 500, color);

	VectorSet(origin, -120, -150, 250);
	color.r = color.g = 1.0f;
	color.b = 1.0f;
	ob = AddBox(Scene1, origin, 100, 100, 50, color);
	ob->reflect = 0.2f;
	ob->specular = 1.0f;

	VectorSet(origin, 0, 1, 0); // facing up
	Normalize(origin);
	color.r = 1.0f;
	color.g = 1.0f;
	color.b = 1.0f;
	AddPlane(Scene1, origin, -200, color);

	VectorSet(origin, 0, 0, -1); // facing back at camera
	color.r = 1.0f;
	color.g = 1.0f;
	color.b = 1.0f;
	AddPlane(Scene1, origin, -500, color);

	VectorSet(origin, 0, 0, 1);
	color.r = color.g = color.b = 1.0f;
	AddPlane(Scene1, origin, -300, color);

	VectorSet(origin, 0, -150, 250);
	color.r = color.g = 0.0f;
	color.b = 1.0f;
	AddLight(Scene1, origin, 300, color);

	VectorSet(normal, 0, 1, -1);
	Normalize(normal);
	VectorSet(origin, -100, 90, 290);
	color.r = color.g = color.b = 1.0f;
	color.b = 0.0f;
	AddDisc(Scene1, normal, origin, 90, color);
}

void WriteTGAFile (const char *filename)
{
	int i;
	unsigned char *writeBuffer;
	FILE *f = fopen(filename, "wb");

	if (!f)
	{
		printf("Unable to save file to %s\n", filename);
		return;
	}

	writeBuffer = new unsigned char[18+SCREEN_WIDTH*SCREEN_HEIGHT*3];
	memset(writeBuffer, 0, 18); // set header to all 0 for starters, then modify appropriately
	writeBuffer[2] = 2; // set tga type to RGB color
	// set width and height bytes
	writeBuffer[12] = SCREEN_WIDTH & 255;
	writeBuffer[13] = SCREEN_WIDTH >> 8;
	writeBuffer[14] = SCREEN_HEIGHT & 255;
	writeBuffer[15] = SCREEN_HEIGHT >> 8;
	writeBuffer[16] = 24; // set 24 bits per pixel

	for (i=0; i < SCREEN_WIDTH*SCREEN_HEIGHT; i++)
	{
		// write out bytes, in BGR order
		writeBuffer[18+i*3+0] = (unsigned char)(255*OutputBuffer[i].b);
		writeBuffer[18+i*3+1] = (unsigned char)(255*OutputBuffer[i].g);
		writeBuffer[18+i*3+2] = (unsigned char)(255*OutputBuffer[i].r);
	}

	size_t items = fwrite(writeBuffer, 18+SCREEN_WIDTH*SCREEN_HEIGHT*3, 1, f);
	delete[] writeBuffer;
	fclose(f);

	if (items != 1)
		printf("Unable to save file to %s\n", filename);
}
